Imports pixolut.BizBlox
Imports System.Reflection
Imports System.io
Imports System.Text

Public MustInherit Class AbstractRenderEngine

    Private tables As TableList
    Private updateHnd As UpdateProgress
    Private statusHnd As UpdateStatus
    Private fileParserType As Type

    Public Delegate Sub UpdateProgress(ByVal current As Integer, ByVal max As Integer)
    Public Delegate Sub UpdateStatus(ByVal str As String)

    'Protected MustOverride Function RenderCore(ByVal t As Table) As String
    Protected MustOverride Function RenderPrefix(ByVal t As Table) As String
    Protected MustOverride Function RenderPostfix(ByVal t As Table) As String

    'New Stuff
    Protected MustOverride Function RenderElements(ByVal t As Table) As String
    Protected MustOverride Function RenderRegionStart() As String
    Protected MustOverride Function RenderRegionEnd() As String
    Protected MustOverride Function RenderColumn(ByVal t As Table, ByVal index As Integer) As String
    Protected MustOverride Function RenderForeignColumn(ByVal t As Table, ByVal index As Integer) As String
    Protected MustOverride Function FormatFilename(ByVal tableName As String) As String
    Protected MustOverride Function FormatFiletype() As String
    Protected MustOverride Function FormatComment() As String
    Protected MustOverride Function RenderAttributes(ByVal attributes() As Attribute) As String

    Private Const X_ELEMENTS As String = "+Elements"
    Private Const X_FOREIGN As String = ".ForeignReference"


    Public Sub New(ByVal u As UpdateProgress, ByVal s As UpdateStatus, ByVal fileParser As Type)
        updateHnd = u
        statusHnd = s
        fileParserType = fileParser
    End Sub

    Private m_project As BBXProject
    Public Property Project() As BBXProject
        Get
            Return m_project
        End Get
        Set(ByVal value As BBXProject)
            m_project = value
        End Set
    End Property

    Public Sub Import(ByVal InputFilename As String)
        Dim src As String = ReadFromDisk(InputFilename)
        Dim obj As AbstractFileParser = CType(fileParserType.InvokeMember(Nothing, BindingFlags.DeclaredOnly Or BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.CreateInstance, Nothing, Nothing, New Object() {}), AbstractFileParser)
        tables = obj.Parse(src, Me.Project)
    End Sub

    Public Sub OutputClasses(ByVal OutputPath As String, ByVal tables As TableList)
        For i As Integer = 0 To tables.Count - 1
            Output(tables(i), OutputPath & "\" & FormatFilename(tables(i).TableName) & FormatFiletype())
            updateHnd((i + 1), tables.Count)
        Next
    End Sub

    Private Sub FileToDisk(ByVal src As String, ByVal filePath As String)
        'Try
        Dim out As StreamWriter
        out = File.CreateText(filePath)
        out.Write(src.Trim())
        out.Flush()
        out.Close()
        'Catch ex As Exception
        '    MsgBox("Fatal error writing to disk." & vbCrLf & ex.Message, MsgBoxStyle.Critical Or MsgBoxStyle.OKOnly, "BizBlox Error")
        '    End
        'End Try
    End Sub

    Private Function ReadFromDisk(ByVal filename As String) As String
        'Try
        Dim src As StreamReader
        Dim inData As String
        src = File.OpenText(filename)
        inData = src.ReadToEnd()
        src.Close()
        Return inData.Trim()
        'Catch ex As Exception
        '    MsgBox("Fatal error reading from disk." & vbCrLf & ex.Message, MsgBoxStyle.Critical Or MsgBoxStyle.OKOnly, "BizBlox Error")
        '    End
        'End Try
    End Function

    Private Sub Output(ByVal t As Table, ByVal p As String)
        If File.Exists(p) Then
            Dim str As String = ReadFromDisk(p)
            Try
                Dim mc As New MarkerCollection(str, AddressOf Me.FormatComment)
                Dim newCore As String = RenderCore(t, mc)
                Dim updated As Boolean = True
                Dim newEntity As String = InjectEntity(str, newCore, updated)
                If updated Then
                    statusHnd("Injecting " & Path.GetFileName(p))
                    FileToDisk(newEntity, p)
                Else
                    statusHnd("No Update: " & Path.GetFileName(p))
                End If
            Catch ex As Exception
                Debug.Print(ex.ToString())
                statusHnd("Failed updating " & Path.GetFileName(p) & " (" & ex.Message & ")")
            End Try
        Else
            statusHnd("Creating " & Path.GetFileName(p))
            Try
                Dim mc As New MarkerCollection(AddressOf Me.FormatComment)
                Dim core As String = Me.RenderCore(t, mc)
                FileToDisk(RenderEntity(t, core), p)
            Catch ex As Exception
                Debug.Print(ex.ToString())
                statusHnd("Failed creating " & Path.GetFileName(p) & " (" & ex.Message & ")")
            End Try
        End If
    End Sub

    Private Function InjectEntity(ByVal src As String, ByVal newCore As String, ByRef updated As Boolean) As String
        Dim l() As String = src.Split(vbLf)
        Dim existingCore As String = ""
        Dim existingLastUpdatedString As String = ""
        Dim sb As New StringBuilder
        Dim DoOutput As Boolean = True

        For i As Integer = 0 To l.Length - 1
            'updateHnd(i, l.Length - 1)
            l(i) = l(i).TrimEnd(vbCr, vbLf, vbCrLf)
            l(i) = l(i) & vbCrLf
            If DoOutput Then
                If l(i).IndexOf(Constants.START_TAG) > -1 Then
                    DoOutput = False
                Else
                    sb = sb.Append(l(i))
                End If
            Else
                If l(i).IndexOf(Constants.END_TAG) > -1 Then
                    DoOutput = True
                    'Only perform the injection if the core has changed from the file version.
                    'This saves updating every file if only one has changed, so reduces need to
                    'commit new versions in source control
                    If newCore = existingCore Then
                        updated = False
                        Return ""
                    Else
                        updated = True
                        sb = sb.Append(FormatComment()).Append(Constants.START_TAG).Append(vbCrLf)
                        sb = sb.Append(Me.GetLastUpdatedString())
                        sb = sb.Append(newCore)
                        sb = sb.Append(FormatComment()).Append(Constants.END_TAG).Append(vbCrLf)
                    End If
                Else
                    If l(i).IndexOf(Constants.LASTUPDATED_TAG) < 0 Then
                        'Save to the existing core string, so we can compare it to the new core
                        'to see if it has changed. Exclude the last updated line
                        existingCore &= l(i)
                    End If
                End If
            End If
        Next
        Return sb.ToString()
    End Function

    Private Function RenderEntity(ByVal t As Table, ByVal src As String) As String
        Dim sb As New StringBuilder
        sb = sb.Append(RenderPrefix(t)).Append(vbCrLf)
        sb = sb.Append(FormatComment()).Append(Constants.START_TAG).Append(vbCrLf)
        sb = sb.Append(Me.GetLastUpdatedString())
        sb = sb.Append(src)
        sb = sb.Append(FormatComment()).Append(Constants.END_TAG).Append(vbCrLf)
        sb = sb.Append(RenderPostfix(t)).Append(vbCrLf)
        Return sb.ToString()
    End Function

    Protected Function GetLastUpdatedString() As String
        Dim sb As New StringBuilder
        sb.Append(FormatComment()).Append(Constants.LASTUPDATED_TAG).Append(Now.ToString("F")).Append(vbCrLf)
        Return sb.ToString()
    End Function

    Protected Function GetVersionString(ByVal sb As StringBuilder) As StringBuilder
        sb = sb.Append(FormatComment()).Append("BizBlox Version : ").Append((New DataAccess).GetType().Assembly.GetName.Version.ToString()).Append(vbCrLf)
        Return sb
    End Function

    Private Function RenderCore(ByVal t As Table, ByVal mc As MarkerCollection) As String
        Dim m As Marker
        Dim sb As New StringBuilder
        sb = Me.GetVersionString(sb)
        sb = sb.Append(RenderRegionStart()).Append(vbCrLf)
        m = mc.GetMarker(X_ELEMENTS)
        If m Is Nothing Then m = New Marker(X_ELEMENTS)
        If Not m.DoNotModify Then m.Update(Me.RenderElements(t), AddressOf Me.FormatComment)
        sb = sb.Append(m.ToString())

        For j As Integer = 0 To t.ColumnCount - 1
            m = mc.GetMarker(t(j).ColumnName)
            If m Is Nothing Then m = New Marker(t(j).ColumnName)
            If Not m.DoNotModify Then m.Update(Me.RenderColumn(t, j), AddressOf Me.FormatComment)
            sb = sb.Append(m.ToString())

            If t(j).FKColumn = True Then
                m = mc.GetMarker(t(j).ColumnName & X_FOREIGN)
                If m Is Nothing Then m = New Marker(t(j).ColumnName & X_FOREIGN)
                If Not m.DoNotModify Then m.Update(Me.RenderForeignColumn(t, j), AddressOf Me.FormatComment)
                sb = sb.Append(m.ToString())
            End If
        Next
        sb = sb.Append(RenderRegionEnd()).Append(vbCrLf)
        Return sb.ToString()
    End Function
End Class
